﻿//StrategyTracer.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.Strategies
{
	using System;
	using System.Collections.Generic;

	using Ecng.Collections;
	using Ecng.Common;

	using StockSharp.Algo.Storages;
	using StockSharp.Algo.Testing;
	using StockSharp.BusinessEntities;

	/// <summary>
	/// Режимы трассировки.
	/// </summary>
	public enum StrategyTracingMode
	{
		/// <summary>
		/// Без трассировки.
		/// </summary>
		NoTrace,

		/// <summary>
		/// Писать изменения.
		/// </summary>
		WriteTrace,

		/// <summary>
		/// Читать изменения.
		/// </summary>
		ReadTrace
	}

	/// <summary>
	/// Трассировщик заявок стратегии.
	/// </summary>
	public class StrategyTracer : Disposable
	{
		private readonly IMarketDataStorage<OrderTraceItem> _storage;
		private readonly Strategy _strategy;
		private readonly SynchronizedList<OrderTraceItem> _items = new SynchronizedList<OrderTraceItem>();

		private DateTime _lastFlushTime;
		private readonly TimeSpan _flushInterval;

		/// <summary>
		/// Создать <see cref="StrategyTracer"/>.
		/// </summary>
		/// <param name="strategy">Стратегия.</param>
		/// <param name="storage">Хранилище.</param>
		public StrategyTracer(Strategy strategy, IMarketDataStorage<OrderTraceItem> storage)
		{
			if (strategy == null)
				throw new ArgumentNullException("strategy");

			if (strategy.Trader == null)
				throw new ArgumentException("Шлюз для стратегии {0} не инициализирован.".Put(strategy), "strategy");

			_strategy = strategy;

			_strategy.OrderRegistering += OnOrderRegistering;
			_strategy.OrderRegistered += OnOrderRegistered;
			_strategy.OrderReRegistering += OnOrderReRegistering;
			_strategy.OrderCanceling += OnOrderCanceling;
			_strategy.NewMyTrades += OnNewMyTrades;
			_strategy.OrderChanged += OnOrderChanged;
			_strategy.OrderRegisterFailed += OnOrderFailed;
			_strategy.OrderCancelFailed += OnOrderFailed;

			_storage = storage;
			_lastFlushTime = DateTime.Now;
			_flushInterval = TimeSpan.FromSeconds(10);
		}

		/// <summary>
		/// Событие появления нового элемента трассировки.
		/// </summary>
		public event Action<OrderTraceItem> NewTraceItem;

		/// <summary>
		/// Освободить ресурсы.
		/// </summary>
		protected override void DisposeManaged()
		{
			_strategy.OrderRegistering -= OnOrderRegistering;
			_strategy.OrderRegistered -= OnOrderRegistered;
			_strategy.OrderReRegistering -= OnOrderReRegistering;
			_strategy.OrderCanceling -= OnOrderCanceling;
			_strategy.NewMyTrades -= OnNewMyTrades;
			_strategy.OrderChanged -= OnOrderChanged;
			_strategy.OrderRegisterFailed -= OnOrderFailed;
			_strategy.OrderCancelFailed -= OnOrderFailed;

			Flush();

			base.DisposeManaged();
		}

		/// <summary>
		/// Сохранить в файл.
		/// </summary>
		private void Flush()
		{
			if (null != _storage)
			{
				lock (_items.SyncRoot)
				{
					if (_items.Count > 0)
					{
						_storage.Save(_items);
						_items.Clear();
					}
				}
			}

			_lastFlushTime = DateTime.Now;
		}

		private void AddItem(OrderTraceItem item)
		{
			if (item == null)
				throw new ArgumentNullException("item");

			//if (item.Order.TransactionId <= 0)
			//	throw new ArgumentOutOfRangeException("item", item.Order.TransactionId, "Неправильный номер заявки.");

			//if (item.Time == default(DateTime))
			//	throw new ArgumentOutOfRangeException("item", item.Time, "Неправильное время.");

			if (item.Order.Price <= 0)
				throw new ArgumentOutOfRangeException("item", item.Order.Price, "Неправильная цена заявки.");

			if (item.Order.Volume < 0)
				throw new ArgumentOutOfRangeException("item", item.Order.Volume, "Неправильный объем заявки.");

			item.LocalTime = item.Order.Security.GetMarketTime();
			
			NewTraceItem.SafeInvoke(item);

			if (_storage != null)
			{
				_items.Add(item);

				if (DateTime.Now - _lastFlushTime > _flushInterval)
					Flush();
			}
		}

		private static Order CloneOrder(Order order)
		{
			return new Order
			{
				Security = order.Security,
				Price = order.Price,
				Volume = order.Volume,
				Balance = order.Balance,
				Status = order.Status,
				State = order.State,
				Direction = order.Direction,
				Id = order.Id,
				TransactionId = order.TransactionId,
				LastChangeTime = order.LastChangeTime
			};
		}

		private void OnOrderReRegistering(Order order, Order newOrder)
		{
			AddItem(new OrderTraceItem
			{
				Order = CloneOrder(newOrder),
				OldOrderId = order.Id,
				OldOrderTransactionId = order.TransactionId,
				Time = order.Security.GetMarketTime(),
				Action = OrderTraceActions.ReRegistering
			});
		}

		private void OnOrderRegistered(Order order)
		{
			AddItem(new OrderTraceItem
			{
				Order = CloneOrder(order),
				Time = order.LastChangeTime,
				Action = OrderTraceActions.Registered
			});
		}

		private void OnOrderCanceling(Order order)
		{
			AddItem(new OrderTraceItem
			{
				Order = CloneOrder(order),
				OldOrderId = order.Id,
				OldOrderTransactionId = order.TransactionId,
				Time = order.Security.GetMarketTime(),
				Action = OrderTraceActions.Canceling
			});
		}

		private void OnOrderRegistering(Order order)
		{
			AddItem(new OrderTraceItem
			{
				Order = CloneOrder(order),
				OldOrderId = order.Id,
				OldOrderTransactionId = order.TransactionId,
				Time = order.Security.GetMarketTime(),
				Action = OrderTraceActions.Registering
			});
		}

		private void OnNewMyTrades(IEnumerable<MyTrade> trades)
		{
			foreach (var mytrade in trades)
			{
				AddItem(new OrderTraceItem
				{
					Order = CloneOrder(mytrade.Order),
					Trade = mytrade.Trade.Clone(),
					Time = mytrade.Trade.Time,
					Action = OrderTraceActions.NewMyTrade
				});
			}
		}

		private void OnOrderChanged(Order order)
		{
			AddItem(new OrderTraceItem
			{
				Order = CloneOrder(order),
				Time = order.LastChangeTime,
				Action = OrderTraceActions.Changed
			});
		}

		private void OnOrderFailed(OrderFail fail)
		{
			AddItem(new OrderTraceItem
			{
				Order = CloneOrder(fail.Order),
				Time = fail.Order.LastChangeTime,
				Action = OrderTraceActions.Failed
			});
		}
	}

	/// <summary>
	/// Менеджер трассировки.
	/// </summary>
	public class StrategyTracing
	{
		private readonly SynchronizedDictionary<Strategy, StrategyTracer> _tracers = new SynchronizedDictionary<Strategy, StrategyTracer>();

		/// <summary>
		/// Создать <see cref="StrategyTracing"/>.
		/// </summary>
		/// <param name="storageRegistry">Внешнее хранилище.</param>
		/// <param name="basePath">Путь к хранилищу.</param>
		public StrategyTracing(IStorageRegistry storageRegistry, string basePath)
		{
			if (storageRegistry == null)
				throw new ArgumentNullException("storageRegistry");

			StorageRegistry = storageRegistry;
			BasePath = basePath;
		}

		/// <summary>
		/// Внешнее хранилище.
		/// </summary>
		public IStorageRegistry StorageRegistry { get; set; }

		/// <summary>
		/// Путь к хранилищу.
		/// </summary>
		public string BasePath { get; set; }

		/// <summary>
		/// Трассировать стратегию.
		/// </summary>
		/// <param name="strategy">Стратегия.</param>
		/// <param name="mode">Режим трассировки.</param>
		/// <param name="directory">Директория к данным. По-умолчанию совпадает с именем стратегии.</param>
		public void Trace(Strategy strategy, StrategyTracingMode mode, string directory = null)
		{
			if (mode != StrategyTracingMode.WriteTrace)
			{
				var tracer = _tracers.TryGetValue(strategy);

				if (null != tracer)
					tracer.Dispose();

				_tracers.Remove(strategy);
			}
			else if (mode == StrategyTracingMode.WriteTrace)
			{
				if (!_tracers.ContainsKey(strategy))
					_tracers[strategy] = CreateTracer(strategy, directory);
			}

			var trader = strategy.Trader as EmulationTrader;

			if (mode == StrategyTracingMode.ReadTrace)
			{
				if (trader == null)
					throw new InvalidOperationException("Чтение трассировки заявок не совместимо со шлюзом {0} и требует EmulationTrader.".Put(strategy.Trader));

				trader.OrderTraces[strategy] = CreateStorage(strategy, directory);
			}
			else
			{
				if (trader != null)
					trader.OrderTraces.Remove(strategy);
			}
		}

		private StrategyTracer CreateTracer(Strategy strategy, string directory)
		{
			return new StrategyTracer(strategy, CreateStorage(strategy, directory));
		}

		private IMarketDataStorage<OrderTraceItem> CreateStorage(Strategy strategy, string directory)
		{
			//if (null == directory)
			//	directory = strategy.Name;

			return StorageRegistry.GetOrderTraceStorage(strategy.Security);
		}
	}
}